package com.huaxin.common.view;

import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.util.AttributeSet;
import android.util.SparseArray;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.Animation;
import android.view.animation.Animation.AnimationListener;
import android.view.animation.Transformation;
import android.widget.AbsListView;
import android.widget.BaseExpandableListAdapter;
import android.widget.ExpandableListAdapter;
import android.widget.ExpandableListView;

import java.util.ArrayList;
import java.util.List;


public class AnimatedExpandableListView extends ExpandableListView {


    @SuppressWarnings("unused")
    private static final String TAG = AnimatedExpandableListAdapter.class.getSimpleName();


    private static final int ANIMATION_DURATION = 100;


    private AnimatedExpandableListAdapter adapter;

    boolean[] expands;

    public AnimatedExpandableListView(Context context) {
        super(context);
    }


    public AnimatedExpandableListView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }


    public AnimatedExpandableListView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }


    public void setAdapter(ExpandableListAdapter adapter) {
        super.setAdapter(adapter);


        if (adapter instanceof AnimatedExpandableListAdapter) {
            this.adapter = (AnimatedExpandableListAdapter) adapter;
            this.adapter.setParent(this);
        } else {
            throw new ClassCastException(adapter.toString() + " must implement AnimatedExpandableListAdapter");
        }
    }


    /**
     * Expands the given group with an animation.
     *
     * @param groupPos The position of the group to expand
     * @return Returns true if the group was expanded. False if the group was
     * already expanded.
     */
    @SuppressLint("NewApi")
    public boolean expandGroupWithAnimation(int groupPos) {
        if (expands == null) {
            expands = new boolean[adapter.getGroupCount()];
        }
        expands[groupPos] = true;
        boolean lastGroup = groupPos == adapter.getGroupCount() - 1;
        if (lastGroup && Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
            return expandGroup(groupPos, true);
        }

        int groupFlatPos = getFlatListPosition(getPackedPositionForGroup(groupPos));
        if (groupFlatPos != -1) {
            int childIndex = groupFlatPos - getFirstVisiblePosition();
            if (childIndex < getChildCount()) {

                View v = getChildAt(childIndex);
                if (v.getBottom() >= getBottom()) {
                    return expandGroup(groupPos);
                }
            }
        }


        // Let the adapter know that we are starting the animation...
        adapter.startExpandAnimation(groupPos, 0);

        // Finally call expandGroup (note that expandGroup will call
        // notifyDataSetChanged so we don't need to)
        return expandGroup(groupPos);
    }

    public void groupWithAnimation(int groupPos) {
        if (expands == null) {
            expands = new boolean[adapter.getGroupCount()];
        }
        if (expands[groupPos]) {
            collapseGroupWithAnimation(groupPos);
        } else {
            expandGroupWithAnimation(groupPos);
        }
    }

    public boolean collapseGroupWithAnimation(int groupPos) {
        if (expands == null) {
            expands = new boolean[adapter.getGroupCount()];
        }
        expands[groupPos] = false;
        int groupFlatPos = getFlatListPosition(getPackedPositionForGroup(groupPos));
        if (groupFlatPos != -1) {
            int childIndex = groupFlatPos - getFirstVisiblePosition();
            if (childIndex >= 0 && childIndex < getChildCount()) {
                View v = getChildAt(childIndex);
                if (v.getBottom() >= getBottom()) {

                    return collapseGroup(groupPos);
                }
            } else {

                return collapseGroup(groupPos);
            }
        }


        // Get the position of the firstChild visible from the top of the screen
        long packedPos = getExpandableListPosition(getFirstVisiblePosition());
        int firstChildPos = getPackedPositionChild(packedPos);
        int firstGroupPos = getPackedPositionGroup(packedPos);


        firstChildPos = firstChildPos == -1 || firstGroupPos != groupPos ? 0 : firstChildPos;


        adapter.startCollapseAnimation(groupPos, firstChildPos);


        // Force the listview to refresh it's views
        adapter.notifyDataSetChanged();
        return isGroupExpanded(groupPos);
    }


    private int getAnimationDuration() {
        return ANIMATION_DURATION;
    }


    /**
     * Used for holding information regarding the group.
     */
    public static class GroupInfo {
        public boolean animating = false;
        public boolean expanding = false;
        public int firstChildPosition;
        ExpandAnimation animation;
    }


    public static abstract class AnimatedExpandableListAdapter extends BaseExpandableListAdapter {
        private SparseArray<GroupInfo> groupInfo = new SparseArray<>();
        private AnimatedExpandableListView parent;


        private static final int STATE_IDLE = 0;
        private static final int STATE_EXPANDING = 1;
        private static final int STATE_COLLAPSING = 2;


        private void setParent(AnimatedExpandableListView parent) {
            this.parent = parent;
        }


        public int getRealChildType(int groupPosition, int childPosition) {
            return 0;
        }


        public int getRealChildTypeCount() {
            return 1;
        }


        public abstract View getRealChildView(int groupPosition, int childPosition, boolean isLastChild, View convertView, ViewGroup parent);

        public abstract int getRealChildrenCount(int groupPosition);


        private GroupInfo getGroupInfo(int groupPosition) {
            GroupInfo info = groupInfo.get(groupPosition);
            if (info == null) {
                info = new GroupInfo();
                groupInfo.put(groupPosition, info);
            }
            return info;
        }

        private void startExpandAnimation(int groupPosition, int firstChildPosition) {
            GroupInfo info = getGroupInfo(groupPosition);
            info.animating = true;
            info.firstChildPosition = firstChildPosition;
            info.expanding = true;

        }


        private void startCollapseAnimation(int groupPosition, int firstChildPosition) {
            GroupInfo info = getGroupInfo(groupPosition);
            info.animating = true;
            info.firstChildPosition = firstChildPosition;
            info.expanding = false;


        }


        private void stopAnimation(int groupPosition) {
            GroupInfo info = getGroupInfo(groupPosition);
            info.animating = false;
        }


        /**
         * Override {@link #getRealChildType(int, int)} instead.
         */
        @Override
        public final int getChildType(int groupPosition, int childPosition) {
            GroupInfo info = getGroupInfo(groupPosition);
            if (info.animating) {

                return 0;
            } else {

                return getRealChildType(groupPosition, childPosition) + 1;
            }
        }


        /**
         * Override {@link #getRealChildTypeCount()} instead.
         */
        @Override
        public final int getChildTypeCount() {
            // Return 1 more than the childTypeCount to account for DummyView
            return getRealChildTypeCount() + 1;
        }

        protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
            return new AbsListView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
                    ViewGroup.LayoutParams.WRAP_CONTENT, 0);
        }


        /**
         * Override {@link #getChildView(int, int, boolean, View, ViewGroup)} instead.
         */
        @Override
        public final View getChildView(final int groupPosition, int childPosition, boolean isLastChild, View convertView, final ViewGroup parent) {
            final GroupInfo info = getGroupInfo(groupPosition);


            if (info.animating) {
                // If this group is animating, return the a DummyView...
                if (!(convertView instanceof DummyView)) {
                    convertView = new DummyView(parent.getContext());
                    convertView.setLayoutParams(new AbsListView.LayoutParams(LayoutParams.MATCH_PARENT, 0));
                }


                if (childPosition < info.firstChildPosition) {

                    convertView.getLayoutParams().height = 0;
                    return convertView;
                }


                final ExpandableListView listView = (ExpandableListView) parent;


                final DummyView dummyView = (DummyView) convertView;


                // Clear the views that the dummy view draws.
                dummyView.clearViews();


                // Set the style of the divider
                dummyView.setDivider(listView.getDivider(), parent.getMeasuredWidth(), listView.getDividerHeight());


                // Make measure specs to measure child views
                final int measureSpecW = MeasureSpec.makeMeasureSpec(parent.getWidth(), MeasureSpec.EXACTLY);
                final int measureSpecH = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);


                int totalHeight = 0;
                int clipHeight = parent.getHeight();


                final int len = getRealChildrenCount(groupPosition);
                for (int i = info.firstChildPosition; i < len; i++) {
                    View childView = getRealChildView(groupPosition, i, (i == len - 1), null, parent);

                    LayoutParams p = (LayoutParams) childView.getLayoutParams();
                    if (p == null) {
                        p = (AbsListView.LayoutParams) generateDefaultLayoutParams();
                        childView.setLayoutParams(p);
                    }

                    int lpHeight = p.height;

                    int childHeightSpec;
                    if (lpHeight > 0) {
                        childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY);
                    } else {
                        childHeightSpec = measureSpecH;
                    }

                    childView.measure(measureSpecW, childHeightSpec);
                    totalHeight += childView.getMeasuredHeight();

                    if (totalHeight < clipHeight) {
                        // we only need to draw enough views to fool the user...
                        dummyView.addFakeView(childView);
                    } else {
                        dummyView.addFakeView(childView);

                        // if this group has too many views, we don't want to
                        // calculate the height of everything... just do a light
                        // approximation and break
                        int averageHeight = totalHeight / (i + 1);
                        totalHeight += (len - i - 1) * averageHeight;
                        break;
                    }
                }


                Object o;
                int state = (o = dummyView.getTag()) == null ? STATE_IDLE : (Integer) o;

                if (info.animation == null) {
                    info.animation = new ExpandAnimation(dummyView, info);
                    info.animation.setDuration(this.parent.getAnimationDuration());
                    info.animation.setAnimationListener(new AnimationListener() {


                        @Override
                        public void onAnimationEnd(Animation animation) {
                            stopAnimation(groupPosition);
                            notifyDataSetChanged();
                            if ((Integer) dummyView.getTag() == STATE_COLLAPSING) {
                                listView.collapseGroup(groupPosition);
                            }
                            dummyView.setTag(STATE_IDLE);
                        }


                        @Override
                        public void onAnimationRepeat(Animation animation) {
                        }


                        @Override
                        public void onAnimationStart(Animation animation) {
                        }


                    });
                }
                if (info.expanding && state != STATE_EXPANDING) {
                    info.animation.setParams(0, totalHeight);
                    dummyView.startAnimation(info.animation);
                    dummyView.setTag(STATE_EXPANDING);
                } else if (!info.expanding && state != STATE_COLLAPSING) {
                    info.animation.setParams(totalHeight, 0);
                    dummyView.startAnimation(info.animation);
                    dummyView.setTag(STATE_COLLAPSING);
                }


                return convertView;
            } else {
                return getRealChildView(groupPosition, childPosition, isLastChild, convertView, parent);
            }
        }


        @Override
        public final int getChildrenCount(int groupPosition) {
            GroupInfo info = getGroupInfo(groupPosition);
            if (info.animating) {
                return info.firstChildPosition + 1;
            } else {
                return getRealChildrenCount(groupPosition);
            }
        }


    }


    private static class DummyView extends View {
        private List<View> views = new ArrayList<View>();
        private Drawable divider;
        private int dividerWidth;
        private int dividerHeight;


        public DummyView(Context context) {
            super(context);
        }


        public void setDivider(Drawable divider, int dividerWidth, int dividerHeight) {
            if (divider != null) {
                this.divider = divider;
                this.dividerWidth = dividerWidth;
                this.dividerHeight = dividerHeight;


                divider.setBounds(0, 0, dividerWidth, dividerHeight);
            }
        }


        /**
         * Add a view for the DummyView to draw.
         *
         * @param childView View to draw
         */
        public void addFakeView(View childView) {
            childView.layout(0, 0, getWidth(), childView.getMeasuredHeight());
            views.add(childView);
        }

        @Override
        protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
            super.onLayout(changed, left, top, right, bottom);
            final int len = views.size();
            for (int i = 0; i < len; i++) {
                View v = views.get(i);
                v.layout(left, top, left + v.getMeasuredWidth(), top + v.getMeasuredHeight());
            }
        }


        public void clearViews() {
            views.clear();
        }


        @Override
        public void dispatchDraw(Canvas canvas) {
            canvas.save();
            if (divider != null) {
                divider.setBounds(0, 0, dividerWidth, dividerHeight);
            }

            final int len = views.size();
            for (int i = 0; i < len; i++) {
                View v = views.get(i);

                canvas.save();
                canvas.clipRect(0, 0, getWidth(), v.getMeasuredHeight());
                v.draw(canvas);
                canvas.restore();

                if (divider != null) {
                    divider.draw(canvas);
                    canvas.translate(0, dividerHeight);
                }

                canvas.translate(0, v.getMeasuredHeight());
            }

            canvas.restore();
        }
    }


    private static class ExpandAnimation extends Animation {
        private int baseHeight;
        private int delta;
        private View view;
        LayoutParams params;
        private GroupInfo groupInfo;


        private ExpandAnimation(View v, GroupInfo info) {
            view = v;
            groupInfo = info;
            params = (LayoutParams) view.getLayoutParams();
        }

        public void setParams(int from, int to) {
            baseHeight = from;
            delta = to - from;
            params.height = from;
//            view.requestLayout();
        }


        @Override
        protected void applyTransformation(float interpolatedTime, Transformation t) {
            super.applyTransformation(interpolatedTime, t);
            final int val;
            if (interpolatedTime < 1.0f) {
                val = baseHeight + (int) (delta * interpolatedTime);
            } else {
                val = baseHeight + delta;
            }
            view.getLayoutParams().height = val;
            view.requestLayout();
        }
    }
}